﻿using System.Text;

namespace DatabaseGroup.SSIS.SourceAdapters.Parser
{
    /// <summary>
    /// Used for parsing field data from an IFileReader
    /// </summary>
    internal class FieldParser : IParsingContext
    {
        // The maximum number of characters we could have in a single field is fixed to 64K.
        // We might want to parametrize this later by exposing it in a component property.
        public const int ParsingBufferMaxSize = 65536;

        private IParsingState _initialParsingState;
        private IParsingState _currentParsingState;
        private IParsingState _rowDelimiterState;

        private readonly StringBuilder _currentText = new StringBuilder();

        /// <summary>
        /// Private constructor: instances created via the BuildParser* methods
        /// </summary>
        private FieldParser()
        {
        }

        /// <summary>
        /// Gets the parse state based on currently matching a row delimiter
        /// </summary>
        public bool RowDelimiterMatch
        {
            get
            {
                return _currentParsingState == _rowDelimiterState;
            }
        }

        /// <summary>
        /// Gets the current field string value
        /// </summary>
        public string CurrentText
        {
            get { return _currentText.ToString(); }
        }

        /// <summary>
        /// Parses the next field in the file
        /// </summary>
        /// <param name="reader">The file to parse</param>
        public void ParseNext(IFileReader reader)
        {
            ParseNext(reader, null);
        }

        /// <summary>
        /// Parses the next field in the file
        /// </summary>
        /// <param name="reader">The file to parse</param>
        /// <param name="rowParsingContext">If not null, the row context will have field data added to it and be used if an exception occurs</param>
        public void ParseNext(IFileReader reader, IRowParsingContext rowParsingContext)
        {
            ResetParsingState();

            ParsingResult parsingResult = ParsingResult.Match;
            char currentChar = '\0';

            do
            {
                if (_currentText.Length >= ParsingBufferMaxSize)
                {
                    ParsingBufferOverflowException ex = new ParsingBufferOverflowException();
                    if (rowParsingContext != null)
                    {
                        ex.ColumnIndex = rowParsingContext.ColumnCount;
                    }
                    throw ex;
                }

                if (parsingResult != ParsingResult.Miss)
                {
                    currentChar = reader.GetNextChar();
                    if (reader.IsEOF)
                    {
                        break;
                    }
                    if (rowParsingContext != null)
                    {
                        rowParsingContext.Append(currentChar);
                    }
                }

                if (_currentParsingState != null)
                {
                    parsingResult = _currentParsingState.ProcessCharacter(this, currentChar);
                }
                else
                {
                    // We should not get into this state for our parsing.
                    // It would mean our graph of states is not connected properly.
                    throw new FieldParsingException(MessageStrings.BadParsingGraphError);
                }
            }
            while (parsingResult != ParsingResult.Done);
        }

        /// <summary>
        /// Resets the current parsing state
        /// </summary>
        public void ResetParsingState()
        {
            _currentParsingState = _initialParsingState;
            _currentText.Length = 0;
        }

        /// <summary>
        /// Initializes a new instance of the FieldParser class 
        /// matching only a single delimiter state
        /// </summary>
        /// <param name="delimiter">The delimiter to match</param>
        /// <returns>Instance of FieldParser</returns>
        public static FieldParser BuildParserWithSingleDelimiter(string delimiter)
        {
            ArgumentVerifier.CheckStringArgument(delimiter, "delimiter");

            FieldParser parser = new FieldParser();

            // TODO: really don't like calling private fields like this
            // TODO: consider changing to private set properties
            parser._initialParsingState = new ParsingState();

            MatchTokenParsingState delimiterState = new MatchTokenParsingState(delimiter);

            parser._initialParsingState.AddNextState(delimiterState);

            delimiterState.SetErrorState(parser._initialParsingState);

            return parser;
        }

        /// <summary>
        /// Initializes a new instance of the FieldParser class 
        /// matching field and row delimiter states
        /// </summary>
        /// <param name="delimiter">The delimiter to match</param>
        /// <param name="rowDelimiter">The row delimited to match</param>
        /// <returns>Instance of FieldParser</returns>
        public static FieldParser BuildParserWithTwoDelimiters(string delimiter, string rowDelimiter)
        {
            ArgumentVerifier.CheckStringArgument(delimiter, "delimiter");
            ArgumentVerifier.CheckStringArgument(rowDelimiter, "rowDelimiter");

            FieldParser parser = new FieldParser();

            parser._initialParsingState = new ParsingState();

            parser._rowDelimiterState = new MatchTokenParsingState(rowDelimiter);
            MatchTokenParsingState delimiterState = new MatchTokenParsingState(delimiter);

            parser._initialParsingState.AddNextState(parser._rowDelimiterState);
            parser._initialParsingState.AddNextState(delimiterState);

            parser._rowDelimiterState.SetErrorState(parser._initialParsingState);

            delimiterState.SetErrorState(parser._initialParsingState);

            return parser;
        }

        /// <summary>
        /// Initializes a new instance of the FieldParser class 
        /// matching only a single delimiter state with a text qualifier
        /// </summary>
        /// <param name="delimiter">The delimiter to match</param>
        /// <param name="qualifier">The qualifier to use for text runs</param>
        /// <returns>Instance of FieldParser</returns>
        public static FieldParser BuildParserWithSingleDelimiterAndQualifier(string delimiter, string qualifier)
        {
            ArgumentVerifier.CheckStringArgument(delimiter, "delimiter");
            ArgumentVerifier.CheckStringArgument(qualifier, "qualifier");

            FieldParser parser = new FieldParser();

            parser._initialParsingState = new ParsingState();
            ParsingState fieldState = new ParsingState();
            ParsingState qualifiedFieldState = new ParsingState();

            MatchTokenParsingState firstQualifierState = new MatchTokenParsingState(qualifier);
            MatchTokenParsingState delimiterState = new MatchTokenParsingState(delimiter);
            MatchEscapedTokenParsingState secondQualifierState = new MatchEscapedTokenParsingState(qualifier);

            parser._initialParsingState.AddNextState(firstQualifierState);
            parser._initialParsingState.AddNextState(fieldState);

            fieldState.AddNextState(delimiterState);

            qualifiedFieldState.AddNextState(secondQualifierState);

            firstQualifierState.AddNextState(qualifiedFieldState);
            firstQualifierState.SetErrorState(fieldState);

            delimiterState.SetErrorState(fieldState);

            secondQualifierState.AddNextState(fieldState);
            secondQualifierState.SetErrorState(qualifiedFieldState);

            return parser;
        }

        /// <summary>
        /// Initializes a new instance of the FieldParser class 
        /// matching field and row delimiter states with a text qualifier
        /// </summary>
        /// <param name="delimiter">The delimiter to match</param>
        /// <param name="rowDelimiter">The row delimited to match</param>
        /// <param name="qualifier">The qualifier to use for text runs</param>
        /// <returns>Instance of FieldParser</returns>
        public static FieldParser BuildParserWithTwoDelimitersAndQualifier(string delimiter, string rowDelimiter, string qualifier)
        {
            ArgumentVerifier.CheckStringArgument(delimiter, "delimiter");
            ArgumentVerifier.CheckStringArgument(rowDelimiter, "rowDelimiter");
            ArgumentVerifier.CheckStringArgument(qualifier, "qualifier");

            FieldParser parser = new FieldParser();

            parser._initialParsingState = new ParsingState();
            ParsingState fieldState = new ParsingState();
            ParsingState qualifiedFieldState = new ParsingState();

            MatchTokenParsingState firstQualifierState = new MatchTokenParsingState(qualifier);
            parser._rowDelimiterState = new MatchTokenParsingState(rowDelimiter);
            MatchTokenParsingState delimiterState = new MatchTokenParsingState(delimiter);
            MatchEscapedTokenParsingState secondQualifierState = new MatchEscapedTokenParsingState(qualifier);

            parser._initialParsingState.AddNextState(firstQualifierState);
            parser._initialParsingState.AddNextState(fieldState);

            fieldState.AddNextState(parser._rowDelimiterState);
            fieldState.AddNextState(delimiterState);

            qualifiedFieldState.AddNextState(secondQualifierState);

            firstQualifierState.AddNextState(qualifiedFieldState);
            firstQualifierState.SetErrorState(fieldState);

            parser._rowDelimiterState.SetErrorState(fieldState);

            delimiterState.SetErrorState(fieldState);

            secondQualifierState.AddNextState(fieldState);
            secondQualifierState.SetErrorState(qualifiedFieldState);

            return parser;
        }

        #region IParsingContext Members

        /// <summary>
        /// Returns the current parse state based on the last character parsed
        /// </summary>
        public IParsingState CurrentState
        {
            get { return _currentParsingState; }
            set { _currentParsingState = value; }
        }

        /// <summary>
        /// Adds a character to the current field value
        /// </summary>
        /// <param name="ch">The character to add</param>
        public void Append(char ch)
        {
            _currentText.Append(ch);
        }

        /// <summary>
        /// Adds a string to the current field value
        /// </summary>
        /// <param name="text">The string to add</param>
        public void Append(string text)
        {
            _currentText.Append(text);
        }

        #endregion
    }
}
